package edu.mit.csail.relo.intro;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;

public class ReloJavaConnectionUtils {
    static final Logger s_logger = ReloIntroPlugin.getLogger(ReloJavaConnectionUtils.class);

	public static final String ReloJDT_PLUGIN_ID = "edu.mit.csail.relo.jdt";
	public static final String ReloJDTBuilder_NATURE_ID = ReloJDT_PLUGIN_ID + ".builder.ReloJDTBuilderProjectNature";
	public static final String JDTBuilder_NATURE_ID = "org.eclipse.jdt.core.javanature";

	public static void getConnectedProjects(List connectedProjects) {
		IProject[] projects = ReloIntroPlugin.getWorkspace().getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) {
			try {
				if (!projects[i].isOpen()) continue;	// ignore closed projects
			    if (javaProject(projects[i]) && connected(projects[i])) connectedProjects.add(projects[i]);
			} catch (CoreException e) {
				s_logger.error("error while getting project descritpion (... to set nature)", e);
			}
		}
	}
	
	public static void getDisconnectedJavaProjects(List disconnectedProjects) {
		IProject[] projects = ReloIntroPlugin.getWorkspace().getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) {
			try {
				if (!projects[i].isOpen()) continue;	// ignore closed projects
			    if (javaProject(projects[i]) && !connected(projects[i])) disconnectedProjects.add(projects[i]);
			} catch (CoreException e) {
				s_logger.error("error while getting project descritpion (... to set nature)", e);
			}
		}
	}
	
	/**
	 * this is a util for potentially long-running job, when there is a need to
	 * create a job and schedule it
	 * 
	 * if caller doesn't want a job created and wants these to run in the same
	 * processor then it needs to send a NullProgressMonitor
	 * 
	 */
	public static void scheduleLongJob(String jobName, final IRunnable runnable) {
		Job connectionJob = new Job(jobName) {
			protected IStatus run(IProgressMonitor monitor) {
				try {
					runnable.run(monitor);
				} catch (Throwable t) {
					// catching the exception allows us to deal with it through our logging framework
					// since there are 'component' boundaries, they should (1) not return anything, and 
					// (2) not return exceptions ... if we want we could return an error here, but
					// then we would be processing it twice
					s_logger.error("Unexpected exception", t);
				}
				return Status.OK_STATUS;
			}
		};
		connectionJob.setPriority(Job.LONG);
		connectionJob.schedule();
	}

	public static void connect(IProject project) throws CoreException {
	    connect(project, null);
	}

	public static void connect(IProject project, IProgressMonitor monitor) throws CoreException {
		if (connected(project)) return;

		s_logger.info("Connecting to: " + project);
		
	    IProjectDescription descr = project.getDescription();
	    String natures[] = descr.getNatureIds();
	    
	    String newNatures[] = new String[natures.length+1];
	    System.arraycopy(natures, 0, newNatures, 0, natures.length);
	    newNatures[natures.length] = ReloJDTBuilder_NATURE_ID;
	    
	    descr.setNatureIds(newNatures);
	    project.setDescription(descr, monitor);
	    
	    // launch a build - this should happen automatically but it doesn't seem
		// to happen when greater than 2 projects are connected - possibly
		// because of an Eclipse bug
	    //project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
		//s_logger.info("Touching: " + project);

		// we move touching, etc, to the nature, that way the build will always happen on connection
	    //project.touch(monitor);

	    // verify - just in case
	    if (!connected(project)) s_logger.error("Failed to connect nature");
	}

	public static boolean connected(IProject project) throws CoreException  {
	    IProjectDescription descr = project.getDescription();
	    return descr.hasNature(ReloJDTBuilder_NATURE_ID);
	}
	private static boolean javaProject(IProject project) throws CoreException  {
	    IProjectDescription descr = project.getDescription();
	    return descr.hasNature(JDTBuilder_NATURE_ID);
	}

	public static boolean connectedToAllJavaProjects() {
		IProject[] projects = ReloIntroPlugin.getWorkspace().getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) {
			try {
				if (!projects[i].isOpen()) continue;	// ignore closed projects
			    if (javaProject(projects[i]) && !connected(projects[i])) return false;
			} catch (CoreException e) {
				s_logger.error("Error while checking for nature", e);
			}
		}
		return true;
	}
	
	public static void disconnect(IProject project) throws CoreException {
	    disconnect(project, null);
	}

	public static void disconnect(IProject project, IProgressMonitor monitor) throws CoreException {
		if (!connected(project)) return;

		s_logger.info("Disconnecting from: " + project);
		
	    IProjectDescription descr = project.getDescription();
	    String natures[] = descr.getNatureIds();
	    int index = -1;
	    for (int i = 0; i < natures.length; i++) {
	    	if (natures[i].equals(ReloJDTBuilder_NATURE_ID)) {
	    		index = i;
	    		break;
	    	}
	    }
	    String newNatures[] = new String[natures.length - 1];
	    
	    System.arraycopy(natures, 0, newNatures, 0, index);
	    System.arraycopy(natures, index + 1, newNatures, index, natures.length - index - 1);
	
	    descr.setNatureIds(newNatures);
		//s_logger.info("Setting description: " + project);
	    project.setDescription(descr, monitor);
	    
	    // launch a build - this should happen automatically but it doesn't seem
		// to happen when greater than 2 projects are connected - possibly
		// because of an Eclipse bug
	    //project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
		//s_logger.info("Touching: " + project);

		// we move touching, etc, to the nature, that way the build will always happen on connection
	    //project.touch(monitor);
	    
	    // verify - just in case
	    if (connected(project)) s_logger.error("Failed to disconnect nature");
	}

	public static void connectToAllJavaProjects() {
		List projectsToConnect = new ArrayList();
		getDisconnectedJavaProjects(projectsToConnect);
		connectToJavaProjects(projectsToConnect, null);
	}

	public static void connectToJavaProjects(final List projectsToConnect, IProgressMonitor monitor) {
		if (monitor == null) {
			scheduleLongJob("Relo Connection", new IRunnable() {
				void run(IProgressMonitor monitor) {
					connectToJavaProjects(projectsToConnect, monitor);
				}});
			return;
		}

		monitor.beginTask("Relo Connecting to Projects", projectsToConnect.size());
		for (int i = 0; i < projectsToConnect.size(); i++) {
			try {
				connect((IProject) projectsToConnect.get(i), monitor);
			} catch (CoreException e) {
				e.printStackTrace();
			}
			monitor.worked(1);
		}
	}

	public static void disconnectFromAllJavaProjects() {
		List projectsToDisconnect = new ArrayList();
		getConnectedProjects(projectsToDisconnect);
		disconnectFromJavaProjects(projectsToDisconnect, null);
	}
	
	abstract static class IRunnable {
		abstract void run(IProgressMonitor monitor);
	}

	public static void disconnectFromJavaProjects(final List projectsToDisconnect, IProgressMonitor monitor) {
		if (monitor == null) {
			scheduleLongJob("Relo DisConnection", new IRunnable() {
				void run(IProgressMonitor monitor) {
					disconnectFromJavaProjects(projectsToDisconnect, monitor);
				}});
			return;
		}
		
		monitor = new SubProgressMonitor(monitor, 1);
		monitor.beginTask("DisConnecting Relo from Projects", projectsToDisconnect.size());
		for (int i = 0; i < projectsToDisconnect.size(); i++) {
			try {
				disconnect((IProject) projectsToDisconnect.get(i), monitor);
			} catch (CoreException e) {
				e.printStackTrace();
			}
			monitor.worked(1);
		}
	}

}
